using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using MoEngine;
using MoGameLib.BattlerModes;

namespace MoGameLib
{
    public class BattlePathFinder : PathFinder<BattleTile, Battler>{}

    public class BattlerRoute
    {
        Queue<BattleTile> m_routeQueue = new Queue<BattleTile>();

        BattleTile m_Dest;

        public BattleTile DestTile
        {
            get { return m_Dest; }
        }

        Battler m_DestBattler;

        public Battler DestBattler
        {
            get { return m_DestBattler; }
        }

        private BattleTile m_Current;

        public BattleTile Current
        {
            get { return m_Current; }
        }

        public int Count
        {
            get { return m_routeQueue.Count; }
        }

        public void SetRoute(IEnumerable<BattleTile> foundPath)
        {
            Clear();

            foreach (BattleTile tile in foundPath)
            {
                m_routeQueue.Enqueue(tile);
                m_Dest = tile;
            }

            m_Current = m_routeQueue.Dequeue();
        }

        public void SetRoute(IEnumerable<BattleTile> foundPath, Battler destBattler)
        {
            SetRoute(foundPath);
            m_DestBattler = destBattler;
        }

        public void MoveToNext()
        {
            m_Current = m_routeQueue.Dequeue();
        }

        public BattleTile Next()
        {
            return m_routeQueue.Peek();
        }

        public void Clear()
        {
            m_routeQueue.Clear();
            m_Dest = null;
            m_DestBattler = null;
            m_Current = null;
        }
    }

    public class Battler : Graph2D
    {
        public enum MoveResult
        {
            Moving,
            Arrived,
            Failed,
        }

        public enum PathingResult
        {
            Following,
            Reached,
            PathNotFound,          
        }


        static Texture2D TokenOverlayFriendly;
        static Texture2D TokenOverlayEnemy;
        static Texture2D FocusUnderlay;
		static AnimTemplate AnimMeleeAttack;
        static readonly Vector2Int TokenSize = new Vector2Int(72, 72);


        static Battler()
        {
            TokenOverlayFriendly = Core.Content.Load<Texture2D>("Images/GUI/TokenOverlayFriendly");
            TokenOverlayEnemy = Core.Content.Load<Texture2D>("Images/GUI/TokenOverlayEnemy");
            FocusUnderlay = Core.Content.Load<Texture2D>("Images/GUI/SelectionRing");
			AnimMeleeAttack = new AnimTemplate(Core.Content.Load<Texture2D>("Images/Battle/Attack/Sword"), 5, 5, 3, 0.02f);
        }


        private Creature m_Creature;

        public Creature Creature
        {
            get { return m_Creature; }
        }

		public string Name
		{
			get { return m_Creature.Name; }
		}

        public Vector2Int TileLocaction
        {
            get { return m_Tile.Location; }
            set 
            {
                BattleTile tile = m_Map.GetTile(value);

                if (tile == null)
                {
                    throw new Exception(string.Format("tile {0} not found", value));
                }


                Tile = tile;
                RealLoction = value * m_Map.TileSize + m_Map.TileSize / 2;
                m_Anchor = tile;

            }
        }

        BattleTile m_Tile;

        public BattleTile Tile
        {
            get { return m_Tile; }
            protected set
            {
                if (value == null)
                {
                    throw new ArgumentNullException();
                }

                if (m_Tile != value)
                {
                    if (m_Tile != null && m_Tile.Battler == this)
                        m_Tile.Battler = null;
                    m_Tile = value;
                    m_Tile.Battler = this;
                }
            }
        }

        protected void UpdateTileLoc()
        {
            Vector2Int tile_loc = new Vector2Int(
                (int)Math.Round(RealLoction.X) / m_Map.TileSize.X,
                (int)Math.Round(RealLoction.Y) / m_Map.TileSize.Y);
            Tile = m_Map.GetTile(tile_loc);
        }

        BattleTile m_Anchor;

        private float m_Rotation;

        public float Rotation
        {
            get { return m_Rotation; }
            set { m_Rotation = value; }
        }

        public GameVector Facing
        {
            get { return new GameVector(); }
        }

        private BattleTeam m_Team;

        public BattleTeam Team
        {
            get { return m_Team; }
            set { m_Team = value; }
        }
/*
        private BattleTile m_MoveDest;
        //protected bool IsPathInvalid;


        public BattleTile MoveDest
        {
            get { return m_MoveDest; }
            set 
            { 
                m_MoveDest = value;
                //IsPathInvalid = true;
            }
        }*/

        public float MoveSpeed
        {
            get { return 60; }
        }

        private BattleMap m_Map;

        public Battler(Battle battle, Creature creature)
        {
            m_Map = battle.Map;
            m_Creature = creature;

            m_Map.Scene.Attach(this, (int)BattleMapLayer.Battlers);
            /*
            // add to tile, (maybe blocked?)
            GameVector cur_loc = TileLocaction;
            BattleTile tile = m_Map.GetTile(cur_loc.X, cur_loc.Y);

            if (tile != null)
                tile.Battler = this;*/
            InitModes();

            ModeMgr.GotoState(typeof(BattlerModeIdle));
        }


		public override string ToString()
		{
			return string.Format("Battler({0})", Name);
		}
/*
        public virtual void Draw(SpriteBatch spriteBatch, Rectangle tileRect)
        {
            
            Texture2D icon = m_Creature.BattleIcon;
            // tileRect.Inflate((icon.Width - tileRect.Width) / 2, (icon.Height - tileRect.Height) / 2);
            spriteBatch.Draw(m_Creature.BattleIcon, new Vector2(tileRect.X + tileRect.Width/2, tileRect.Y + tileRect.Height/2), null, Color.White, MathHelper.ToRadians(Rotation), new Vector2(icon.Width / 2, icon.Height / 2), 1, SpriteEffects.None, 0);
        }*/

        protected override void Render(Camera2D camera, SpriteBatch spriteBatch)
        {
            base.Render(camera, spriteBatch);

            Rectangle drawRect = new Rectangle(
                (int)RealLoction.X/*(int)Math.Round((double)RealLoction.X)*/ - TokenSize.X / 2,
                (int)RealLoction.Y/*(int)Math.Round((double)RealLoction.Y)*/ - TokenSize.Y / 2,
                TokenSize.X,
                TokenSize.Y);

            if (this == m_Map.FocusedBattler || this == m_Map.SelectedBattler)
            {
                Rectangle focusRect = drawRect;
                focusRect.Inflate(focusRect.Width / 2, focusRect.Height / 2);
                spriteBatch.Draw(FocusUnderlay, focusRect, Color.White);
            }

            spriteBatch.Draw(Creature.BattleToken, drawRect, Color.White);
            spriteBatch.Draw(Team.IsPlayerControlled() ? TokenOverlayFriendly : (/*battler == m_FocusedBattler ? m_FocusedTokenTest : */TokenOverlayEnemy), drawRect, Color.White);

        }

        protected StateManager<BattlerMode> ModeMgr = new StateManager<BattlerMode>();

        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            ModeMgr.Update(gameTime);

			if (!UpdateAttacking(gameTime))
            {
				if (PathingTarget != null)
				{
					UpdatePathingToward(PathingTarget, gameTime);
				}
				else if (PathingDest != null)
				{
					UpdatePathingTo(PathingDest, gameTime);
				}
            }
        } 

        public void OrderMove(BattleTile dest)
        {
            BattlerModeMove moveMode = ModeMgr.FindState(typeof(BattlerModeMove)) as BattlerModeMove;
            moveMode.MoveDest = dest;
            ModeMgr.GotoState(typeof(BattlerModeMove));
        }

        public void OrderAttack(Battler target)
        {
            BattlerModeAttack attackMode = ModeMgr.FindState<BattlerModeAttack>();
            attackMode.AttackTarget = target;
            ModeMgr.GotoState(typeof(BattlerModeAttack));
        }

        BattleTile PathingDest;
        Battler PathingTarget;

        public void SetPathingDest(BattleTile dest)
        {
            PathingDest = dest;
            PathingTarget = null;
        }

        public void SetPathingDest(Battler target)
        {
            PathingTarget = target;
            PathingDest = null;
        }

        public void ClearPathing()
        {
            if (PathingDest != null || PathingTarget != null)
            {
                PathingDest = null;
                PathingTarget = null;
                InvalidatePath();
                m_Anchor = Tile;
            }
        }

        public PathingResult UpdatePathingTo(BattleTile dest, GameTime gameTime)
        {
            if (!HasValidPathTo(dest))
            {
                //Console.WriteLine("--- beware! re-finding path!!!");
                if (!FindPathTo(dest))
                    return PathingResult.PathNotFound;
            }

            return FollowPath(gameTime) ? PathingResult.Reached : PathingResult.Following;
        }

        const float RANGE_CLOSE_ENOUGH = 640;

        public PathingResult UpdatePathingToward(Battler target, GameTime gameTime)
        {
            // if close, move directly to the target
            /*if ((RealLoction - target.RealLoction).LengthSquared() <= RANGE_CLOSE_ENOUGH * RANGE_CLOSE_ENOUGH)
            {
                MoveResult result = MoveTo(target.Tile, gameTime);

                if (result == MoveResult.Failed)
                {
                    if (Tile.GetGridDistTo(target.Tile) == 1)
                        return PathingResult.Reached;
                }

                return result == MoveResult.Arrived ? PathingResult.Reached : PathingResult.Following;
            }
            else
            {*/


                if (!HasValidPathToward(target))
                {
                    //Console.WriteLine("--- beware! re-finding path!!!");
                    if (!FindPathToward(target))
                        return PathingResult.PathNotFound;
                }

                return FollowPath(gameTime) ? PathingResult.Reached : PathingResult.Following;
            //}
        }

        protected bool FollowPath(GameTime gameTime)
        {
            if (Route.Count == 0)
                return true;

            MoveResult result = MoveTo(Route.Next(), gameTime);

            if (result == MoveResult.Arrived)
            {
                Route.MoveToNext();

                return Route.Count == 0;
            }
            else if (result == MoveResult.Failed)
            {
                InvalidatePath();
            }

            return false;
        }

        public bool HasValidPathTo(BattleTile dest)
        {
            return Route.DestTile == dest && Route.Current == m_Anchor;
        }

        public bool HasValidPathToward(Battler target)
        {
            return Route.DestBattler == target && Route.Current == m_Anchor && Route.DestTile == target.Tile;
        }

        public void InvalidatePath()
        {
            Route.Clear();
        }

        protected BattlePathFinder PathFinder = new BattlePathFinder();
        protected BattlerRoute Route = new BattlerRoute();

        public bool FindPathTo(BattleTile dest)
        {
            m_Anchor = Tile;
            if (PathFinder.SearchPath(Tile, dest))
            {
                Route.SetRoute(PathFinder.FoundPath);
                return true;
            }

            Route.Clear();
            return false;
        }

        public bool FindPathToward(Battler target)
        {
            m_Anchor = Tile;
            if (PathFinder.SearchPath(Tile, target.Tile))
            {
                Route.SetRoute(PathFinder.FoundPath, target);
                return true;
            }

            Route.Clear();
            return false;
        }

        protected MoveResult MoveTo(BattleTile dest, GameTime gameTime)
        {
			bool bNoBlockCheck = false;

			if (Tile.GetGridDistTo(dest) <= 1)
			{
				bNoBlockCheck = true;

				if (dest.IsBlocked(this))
					return MoveResult.Failed;
			}
			MoveResult result = MoveTo(dest.RealLocation, gameTime, bNoBlockCheck);

			if (result == MoveResult.Arrived)
				TileLocaction = dest.Location;

			return result;
		}

		protected MoveResult MoveTo(Vector2 destLoc, GameTime gameTime)
		{
			return MoveTo(destLoc, gameTime, false);
		}

		protected MoveResult MoveTo(Vector2 destLoc, GameTime gameTime, bool bNoBlockCheck)
		{
			Vector2 dir = destLoc - RealLoction;
			float dist = dir.Length();
			dir.Normalize();

			float travelDist = MoveSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;

			if (!bNoBlockCheck)
			{
				// TODO: what we really should do here is to do a trace!!!
				float lookAheadDist = BattleMap.MapTileSize.X / 2;

				BattleTile testTile = m_Map.GetTile(m_Map.ToTileLoc(RealLoction + dir * lookAheadDist));

				if (testTile != Tile && testTile.IsBlocked(this))
					return MoveResult.Failed;

				testTile = m_Map.GetTile(m_Map.ToTileLoc(RealLoction + dir * (float)Math.Min(travelDist, dist)));

				if (testTile != Tile && testTile.IsBlocked(this))
					return MoveResult.Failed;
			}


			if (travelDist >= dist)
			{
				RealLoction += dir * dist;
				UpdateTileLoc();
				return MoveResult.Arrived;
			}
			else
			{
				RealLoction += dir * travelDist;
				UpdateTileLoc();
				return MoveResult.Moving;
			}
		}

        private Battler AttackTarget;

        public bool IsInAttackRange(Battler target)
        {
            return Tile.GetGridDistTo(target.Tile) <= 1;
        }

        public bool IsAttacking(Battler target)
        {
			return IsAttacking() && AttackTarget == target;
        }

		public bool IsAttacking()
		{
			return AttackTarget != null;
		}

        public void StartAttack(Battler target)
        {
			if (AttackTarget != target)
			{
				AttackTarget = target;
				bEngagedMelee = false;
			}
        }

        public void StopAttack()
        {
            AttackTarget = null;
			bEngagedMelee = false;
        }

		public bool IsInMeleeEngageRange()
		{
			return (RealLoction - AttackTarget.RealLoction).LengthSquared() <= 100 * 100;
		}

		private float AttackCooldown;
		private bool bEngagedMelee = false;

        public bool UpdateAttacking(GameTime gameTime)
		{
			bool bAttacking = false;
			float totalTimePassed = (float)gameTime.ElapsedGameTime.TotalSeconds;

			
			if (AttackTarget != null)
			{
				if (IsInAttackRange(AttackTarget))
				{
					if (!bEngagedMelee)
					{
						if (IsInMeleeEngageRange())
						{
							bEngagedMelee = true;
							bAttacking = true;
						}
						else
						{
							MoveTo(AttackTarget.RealLoction, gameTime, false);
						}
					}
					else
					{
						bAttacking = true;
					}

				}
				else
				{
					StopAttack();
				}
			}

			if (bAttacking)
			{
				if (AttackCooldown > 0)
				{

					while (totalTimePassed > 0.0f)
					{
						if (totalTimePassed >= AttackCooldown)
						{
							totalTimePassed -= AttackCooldown;
							DoAttack();
						}
						else
						{
							AttackCooldown -= totalTimePassed;
							totalTimePassed = 0.0f;
							break;
						}
					}

				}
				else
				{
					DoAttack();
				}
			}
			else
			{
				if (AttackCooldown > 0.0f)
				{
					AttackCooldown = Math.Max(AttackCooldown - totalTimePassed, 0.0f);
				}

			}
			return IsAttacking();
		}

		public void DoAttack()
		{
			Console.WriteLine("doing 10 damage to {0}", AttackTarget);
			AttackTarget.TakeHit(this, 10, DamageTypes.SuperSlash);
			AttackCooldown = 1.2f;
			Animation vfx = new Animation(AnimMeleeAttack);
			Vector2 attDir = (AttackTarget.RealLoction - RealLoction);
			attDir.Normalize();
			vfx.RealLoction = RealLoction + attDir * TokenSize.X / 2 * 1.1f;
			vfx.Scale = 0.7f;
			Attach(vfx);
		}

		public void TakeHit(Battler attacker, int damage, DamageType damageType)
		{
			Animation vfx = new Animation(damageType.ImpactVFX);
			Vector2 attDir = (attacker.RealLoction - RealLoction);
			attDir.Normalize();
			vfx.RealLoction = RealLoction + attDir * TokenSize.X / 2 * 0.5f +(new Vector2((float)Core.Random.NextDouble(), (float)Core.Random.NextDouble())) * 20;
			vfx.Scale = 0.7f;
			Attach(vfx);
		}

        protected void InitModes()
        {
            ModeMgr.AddState(new BattlerModeIdle(), this);
            ModeMgr.AddState(new BattlerModeMove(), this);
            ModeMgr.AddState(new BattlerModeAttack(), this);
        }
    }

    namespace BattlerModes
    {
        public class BattlerMode : State
        {
            protected Battler Battler;

            public override void Init(object host, BaseStateManager stateManager)
            {
                base.Init(host, stateManager);
                Battler = host as Battler;
            }
        }

        public class BattlerModeIdle : BattlerMode
        {

        }

        /// <summary>
        /// control battler move to a dest, transit to Idle once done
        /// won't engage enemy
        /// </summary>
        public class BattlerModeMove : BattlerMode
        {
            private BattleTile m_Dest;

            public BattleTile MoveDest
            {
                get { return m_Dest; }
                set { m_Dest = value; }
            }

            public override void BeginState(State prevState)
            {
                base.BeginState(prevState);

                Battler.SetPathingDest(MoveDest);
            }

            public override void Update(GameTime gameTime)
            {
                base.Update(gameTime);

                if (MoveDest == null || Battler.Tile == MoveDest)
                {
                    Console.WriteLine("reached destination go to idle mode");
                    Goto(typeof(BattlerModeIdle));
                }
            }
        }

        public class BattlerModeAttack : BattlerMode
        {
            private Battler m_AttackTarget;
            private bool bWasAttacking;

            public Battler AttackTarget
            {
                get { return m_AttackTarget; }
                set { m_AttackTarget = value; }
            }

            public override void BeginState(State prevState)
            {
                base.BeginState(prevState);

                Battler.SetPathingDest(AttackTarget);
                bWasAttacking = false;
            }

			public override void EndState(State nextState)
			{
				base.EndState(nextState);

				Battler.StopAttack();
				Battler.ClearPathing();
			}

            public override void Update(GameTime gameTime)
            {
                base.Update(gameTime);

                bool bShouldAttack = Battler.IsInAttackRange(m_AttackTarget);
				bool bIsAttacking = Battler.IsAttacking(m_AttackTarget);

                if (bShouldAttack != bIsAttacking)
                {
                    if (bShouldAttack)
                    {
						Battler.StartAttack(m_AttackTarget);
                    }
                    else
                    {
                        Battler.StopAttack();
                    }
                }

                if (bWasAttacking && !bIsAttacking)
                {
                    Battler.SetPathingDest(AttackTarget);                  
                }

                bWasAttacking = bIsAttacking;

                /*

                if (Battler.IsInAttackRange(m_AttackTarget))
                {
                    Battler.StartAttack((m_AttackTarget));
                }
                else
                {
                    Battler.StopAttack();
                }*/
            }
        }
    }
}
